﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace NetFrameworkExtensions.Mathematics
{
  public class SMAAccumulator
  {
    double sum;
    double sum2;
    int stepSum;
    int k;
    Queue<Data> queue;

    public SMAAccumulator(int k)
    {
      Contract.Requires(k > 0);
      this.k = k;
      sum = 0;
      sum2 = 0;
      stepSum = 0;
      queue = new Queue<Data>();
    }

    public void Add(double value, int steps = 1)
    {
      Contract.Requires(steps >= 1);
      var newData = new Data { Value = value, Steps = steps };
      int toRemove = stepSum + steps - k;
      while (toRemove > 0)
      {
        if (queue.Count == 0)
          break;
        var frontData = queue.Peek();
        if (frontData.Steps > toRemove)
        {
          frontData.Steps -= toRemove;
          stepSum -= toRemove;
          sum -= frontData.Value * toRemove;
          sum2 -= frontData.Value * frontData.Value * toRemove;
          toRemove = 0;
          break;
        }
        else
        {
          queue.Dequeue();
          stepSum -= frontData.Steps;
          sum -= frontData.Value * frontData.Steps;
          sum2 -= frontData.Value * frontData.Value * frontData.Steps;
          toRemove -= frontData.Steps;
        }
      }
      if (toRemove > 0)
        steps = k;
      queue.Enqueue(new Data { Value = value, Steps = steps });
      stepSum += steps;
      sum += value * steps;
      sum2 += value * value * steps;
    }

    public double Mean { get { return sum / stepSum; } }
    public double Variance 
    { 
      get 
      {
        var m2 = Mean;
        m2 *= m2;
        var mean2 = sum2 / stepSum;
        return m2 > mean2 ? 0 : mean2 - m2;
      } 
    }
    public double StandardDeviation { get { return Math.Sqrt(Variance); } }
    public double K { get { return k; } }
    public double Sum { get { return sum; } }
    public double StepSum { get { return stepSum; } }

    class Data
    {
      public double Value;
      public int Steps;
    }
  }
}
